package com.uuah.server.config.spring.parsers.assembly;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.MapFactoryBean;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
import org.w3c.dom.Attr;

import com.uuah.server.config.spring.UuahHierarchicalBeanDefinitionParserDelegate;
import com.uuah.server.config.spring.parsers.assembly.configuration.IPropertyConfiguration;
import com.uuah.server.config.spring.parsers.assembly.configuration.ISingleProperty;
import com.uuah.server.config.spring.parsers.assembly.configuration.SinglePropertyLiteral;
import com.uuah.server.config.spring.parsers.assembly.configuration.SinglePropertyWrapper;
import com.uuah.server.config.spring.parsers.collection.ChildListEntryDefinitionParser;
import com.uuah.server.config.spring.parsers.collection.ChildMapEntryDefinitionParser;
import com.uuah.server.util.spring.SpringXMLUtils;
import com.uuah.utils.ClassUtils;
import com.uuah.utils.MapCombiner;

/**
 *
 * TODO comment ...
 *
 * <p>update record:updator，update time，update content and version</p>
 * @author <a href="jonny_quan@hotmail.com">jonny</a>
 * @date 2009 2009-3-27 上午10:33:08
 * @version 1.0.0
 */
public class BeanAssembler implements IBeanAssembler {

	private static Logger logger = LoggerFactory.getLogger(BeanAssembler.class);
	private IPropertyConfiguration beanConfig;
	private BeanDefinitionBuilder bean;
	private IPropertyConfiguration targetConfig;
	private BeanDefinition target;

	public BeanAssembler(IPropertyConfiguration beanConfig,
			BeanDefinitionBuilder bean, IPropertyConfiguration targetConfig,
			BeanDefinition target) {
		this.beanConfig = beanConfig;
		this.bean = bean;
		this.targetConfig = targetConfig;
		this.target = target;
	}

	public BeanDefinitionBuilder getBean() {
		return bean;
	}

	protected void setBean(BeanDefinitionBuilder bean) {
		this.bean = bean;
	}

	public BeanDefinition getTarget() {
		return target;
	}

	protected IPropertyConfiguration getBeanConfig() {
		return beanConfig;
	}

	protected IPropertyConfiguration getTargetConfig() {
		return targetConfig;
	}

	public void extendBean(Attr attribute) {
		String oldName = SpringXMLUtils.attributeName(attribute);
		if (!beanConfig.isIgnored(oldName)) {
			if (logger.isDebugEnabled()) {
				logger.debug(attribute + " for "
						+ bean.getBeanDefinition().getBeanClassName());
			}
			String oldValue = attribute.getNodeValue();
			String newName = bestGuessName(beanConfig, oldName, bean
					.getBeanDefinition().getBeanClassName());
			Object newValue = beanConfig.translateValue(oldName, oldValue);
			addPropertyWithReference(bean.getBeanDefinition()
					.getPropertyValues(),
					beanConfig.getSingleProperty(oldName), newName, newValue);
		}
	}

	public void extendBean(String newName, Object newValue, boolean isReference) {
		addPropertyWithReference(bean.getBeanDefinition().getPropertyValues(),
				new SinglePropertyLiteral(isReference), newName, newValue);
	}

	public void extendTarget(Attr attribute) {
		String oldName = SpringXMLUtils.attributeName(attribute);
		String oldValue = attribute.getNodeValue();
		String newName = bestGuessName(targetConfig, oldName, bean
				.getBeanDefinition().getBeanClassName());
		Object newValue = targetConfig.translateValue(oldName, oldValue);
		addPropertyWithReference(target.getPropertyValues(), targetConfig
				.getSingleProperty(oldName), newName, newValue);
	}

	public void extendTarget(String newName, Object newValue,
			boolean isReference) {
		assertTargetPresent();
		addPropertyWithReference(target.getPropertyValues(),
				new SinglePropertyLiteral(isReference), newName, newValue);
	}

	public void extendTarget(String oldName, String newName, Object newValue) {
		assertTargetPresent();
		addPropertyWithReference(target.getPropertyValues(),
				new SinglePropertyWrapper(oldName, getTargetConfig()), newName,
				newValue);
	}

	public void insertBeanInTarget(String oldName) {
		if (logger.isDebugEnabled()) {
			logger.debug("insert "
					+ bean.getBeanDefinition().getBeanClassName() + " -> "
					+ target.getBeanClassName());
		}
		assertTargetPresent();
		String beanClass = bean.getBeanDefinition().getBeanClassName();
		PropertyValues sourceProperties = bean.getRawBeanDefinition()
				.getPropertyValues();
		String newName = bestGuessName(targetConfig, oldName, target
				.getBeanClassName());
		MutablePropertyValues targetProperties = target.getPropertyValues();
		PropertyValue pv = targetProperties.getPropertyValue(newName);
		Object oldValue = null == pv ? null : pv.getValue();

		if (!targetConfig.isIgnored(oldName)) {
			if (targetConfig.isCollection(oldName)
					|| beanClass
							.equals(ChildListEntryDefinitionParser.ListEntry.class
									.getName())) {
				if (null == oldValue) {
					if (beanClass
							.equals(ChildMapEntryDefinitionParser.KeyValuePair.class
									.getName())
							|| beanClass.equals(MapEntryCombiner.class
									.getName())
							|| beanClass.equals(MapFactoryBean.class.getName())) {
						BeanDefinitionBuilder combiner = BeanDefinitionBuilder
								.rootBeanDefinition(MapCombiner.class);
						targetProperties.addPropertyValue(newName, combiner
								.getBeanDefinition());
						MutablePropertyValues combinerProperties = combiner
								.getBeanDefinition().getPropertyValues();
						oldValue = new ManagedList();
						pv = new PropertyValue(MapCombiner.LIST, oldValue);
						combinerProperties.addPropertyValue(pv);
					} else {
						oldValue = new ManagedList();
						pv = new PropertyValue(newName, oldValue);
						targetProperties.addPropertyValue(pv);
					}
				}

				List list = retrieveList(oldValue);
				if (ChildMapEntryDefinitionParser.KeyValuePair.class.getName()
						.equals(beanClass)) {
					list.add(new ManagedMap());
					retrieveMap(list.get(list.size() - 1)).put(
							sourceProperties.getPropertyValue(
									ChildMapEntryDefinitionParser.KEY)
									.getValue(),
							sourceProperties.getPropertyValue(
									ChildMapEntryDefinitionParser.VALUE)
									.getValue());
				} else if (beanClass
						.equals(ChildListEntryDefinitionParser.ListEntry.class
								.getName())) {
					list.add(sourceProperties.getPropertyValue(
							ChildListEntryDefinitionParser.VALUE).getValue());
				} else {
					list.add(bean.getBeanDefinition());
				}
			} else {
				// not a collection

				if (ChildMapEntryDefinitionParser.KeyValuePair.class.getName()
						.equals(beanClass)) {
					if (null == pv || null == oldValue) {
						pv = new PropertyValue(newName, new ManagedMap());
						targetProperties.addPropertyValue(pv);
					}
					retrieveMap(pv.getValue()).put(
							sourceProperties.getPropertyValue(
									ChildMapEntryDefinitionParser.KEY)
									.getValue(),
							sourceProperties.getPropertyValue(
									ChildMapEntryDefinitionParser.VALUE)
									.getValue());
				} else {
					targetProperties.addPropertyValue(newName, bean
							.getBeanDefinition());
				}
			}
		}
	}

	private static List retrieveList(Object value) {
		if (value instanceof List) {
			return (List) value;
		} else if (isDefinitionOf(value, MapCombiner.class)) {
			return (List) unpackDefinition(value, MapCombiner.LIST);
		} else {
			throw new ClassCastException("Collection not of expected type: "
					+ value);
		}
	}

	private static Map retrieveMap(Object value) {
		if (value instanceof Map) {
			return (Map) value;
		} else if (isDefinitionOf(value, MapFactoryBean.class)) {
			return (Map) unpackDefinition(value, "sourceMap");
		} else {
			throw new ClassCastException("Map not of expected type: " + value);
		}
	}

	private static boolean isDefinitionOf(Object value, Class clazz) {
		return value instanceof BeanDefinition
				&& ((BeanDefinition) value).getBeanClassName().equals(
						clazz.getName());
	}

	private static Object unpackDefinition(Object definition, String name) {
		return ((BeanDefinition) definition).getPropertyValues()
				.getPropertyValue(name).getValue();
	}

	public void copyBeanToTarget() {
		if (logger.isDebugEnabled()) {
			logger.debug("copy " + bean.getBeanDefinition().getBeanClassName()
					+ " -> " + target.getBeanClassName());
		}
		assertTargetPresent();
		MutablePropertyValues targetProperties = target.getPropertyValues();
		MutablePropertyValues beanProperties = bean.getBeanDefinition()
				.getPropertyValues();
		for (int i = 0; i < beanProperties.size(); i++) {
			PropertyValue propertyValue = beanProperties.getPropertyValues()[i];
			addPropertyWithoutReference(targetProperties,
					new SinglePropertyLiteral(), propertyValue.getName(),
					propertyValue.getValue());
		}
	}

	public void setBeanFlag(String flag) {
		UuahHierarchicalBeanDefinitionParserDelegate.setFlag(bean
				.getRawBeanDefinition(), flag);
	}

	protected void assertTargetPresent() {
		if (null == target) {
			throw new IllegalStateException(
					"Bean assembler does not have a target");
		}
	}

	protected void addPropertyWithReference(MutablePropertyValues properties,
			ISingleProperty config, String name, Object value) {
		if (!config.isIgnored()) {
			if (config.isReference()) {
				if (value instanceof String) {
					if (((String) value).trim().indexOf(" ") > -1) {
						config.setCollection();
					}
					for (StringTokenizer refs = new StringTokenizer(
							(String) value); refs.hasMoreTokens();) {
						String ref = refs.nextToken();
						if (logger.isDebugEnabled()) {
							logger.debug("possible non-dependent reference: "
									+ name + "/" + ref);
						}
						addPropertyWithoutReference(properties, config, name,
								new RuntimeBeanReference(ref));
					}
				} else {
					throw new IllegalArgumentException(
							"Bean reference must be a String: " + name + "/"
									+ value);
				}
			} else {
				addPropertyWithoutReference(properties, config, name, value);
			}
		}
	}

	protected void addPropertyWithoutReference(
			MutablePropertyValues properties, ISingleProperty config,
			String name, Object value) {
		if (!config.isIgnored()) {
			if (logger.isDebugEnabled()) {
				logger.debug(name + ": " + value);
			}
			Object oldValue = null;
			if (properties.contains(name)) {
				oldValue = properties.getPropertyValue(name).getValue();
			}
			// merge collections
			if (config.isCollection() || oldValue instanceof Collection
					|| value instanceof Collection) {
				Collection values = new ManagedList();
				if (null != oldValue) {
					properties.removePropertyValue(name);
					if (oldValue instanceof Collection) {
						values.addAll((Collection) oldValue);
					} else {
						values.add(oldValue);
					}
				}
				if (value instanceof Collection) {
					values.addAll((Collection) value);
				} else {
					values.add(value);
				}
				properties.addPropertyValue(name, values);
			} else {
				properties.addPropertyValue(name, value);
			}
		}
	}

	protected static String bestGuessName(IPropertyConfiguration config,
			String oldName, String className) {
		String newName = config.translateName(oldName);
		if (!methodExists(className, newName)) {
			String plural = newName + "s";
			if (methodExists(className, plural)) {
				// this lets us avoid setting addCollection in the majority of
				// cases
				config.addCollection(oldName);
				return plural;
			}
			if (newName.endsWith("y")) {
				String pluraly = newName.substring(0, newName.length() - 1)
						+ "ies";
				if (methodExists(className, pluraly)) {
					// this lets us avoid setting addCollection in the majority
					// of cases
					config.addCollection(oldName);
					return pluraly;
				}
			}
		}
		return newName;
	}

	protected static boolean methodExists(String className, String newName) {
		try {
			Class clazz = ClassUtils.getClass(className);
			Method[] methods = clazz.getMethods();
			String setter = "set" + newName;
			for (int i = 0; i < methods.length; ++i) {
				if (methods[i].getName().equalsIgnoreCase(setter)) {
					return true;
				}
			}
		} catch (Exception e) {
			logger.warn("Could not access bean class " + className, e);
		}
		return false;
	}

}
